package org.lds.logger.config.appender.rolling;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.lds.logger.config.appender.rolling.compress.CompressType;
import org.lds.logger.util.CompressUtils;
import org.lds.logger.util.Utils;

import java.io.File;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DefaultRollingStrategy extends AbstractRollingStrategy {

    private static final int DEFAULT_MAX_FILE_SIZE = 500 * 1024 * 1024;
    private static final int DEFAULT_MAX_ALL_FILE_SIZE = 20 * DEFAULT_MAX_FILE_SIZE;
    private static final int DEFAULT_MAX_FILE_COUNT = 20;
    private static final int DEFAULT_MAX_FILE_RESERVE_DAY_COUNT = 20;

    private int maxFileSize = DEFAULT_MAX_FILE_SIZE;
    private int maxAllFileSize = DEFAULT_MAX_ALL_FILE_SIZE;
    private int maxFileCount = DEFAULT_MAX_FILE_COUNT;
    private int maxFileReserveDayCount = DEFAULT_MAX_FILE_RESERVE_DAY_COUNT;
    private CompressType compressType;

    @Override
    public boolean rolling() {
        return getFile().length() >= maxFileSize;
    }

    @Override
    public void execRolling() {
        getFileAppender().closeFile();
        rename();
        setFile(new File(getFileAppender().getDestination()));
        getFileAppender().openFile();
    }

    private void rename() {
        String rollingFileName = getRollingFileName();
        if (compressType == CompressType.NONE) {
            getFile().renameTo(new File(rollingFileName));
        } else {
            CompressUtils.compress(compressType, getFileAppender().getDestination(), rollingFileName);
            getFile().delete();
        }
    }

    private String getRollingFileName() {
        return getRollingFileNamePrefix() + "." + DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS") + getExpandName();
    }

    public static int buildMaxFileSize(String maxFileSize) {
        if (StringUtils.isEmpty(maxFileSize)) return DEFAULT_MAX_FILE_SIZE;
        try {
            int maxFileSizeInt = Integer.valueOf(maxFileSize).intValue();
            if (maxFileSizeInt < 1 || maxFileSizeInt > 1024) return DEFAULT_MAX_FILE_SIZE;
            return maxFileSizeInt * 1024 * 1024;
        } catch (Throwable e) {
            return DEFAULT_MAX_FILE_SIZE;
        }
    }

    public static int buildMaxAllFileSize(String maxAllFileSize) {
        if (StringUtils.isEmpty(maxAllFileSize)) return DEFAULT_MAX_ALL_FILE_SIZE;
        try {
            int maxAllFileSizeInt = Integer.valueOf(maxAllFileSize).intValue();
            if (maxAllFileSizeInt < 1 || maxAllFileSizeInt > 20480) return DEFAULT_MAX_ALL_FILE_SIZE;
            return maxAllFileSizeInt * 1024 * 1024;
        } catch (Throwable e) {
            return DEFAULT_MAX_ALL_FILE_SIZE;
        }
    }

    public static int buildMaxFileCount(String maxFileCount) {
        if (StringUtils.isEmpty(maxFileCount)) return DEFAULT_MAX_FILE_COUNT;
        try {
            int maxFileCountInt = Integer.valueOf(maxFileCount).intValue();
            if (maxFileCountInt < 1 || maxFileCountInt > 50) return DEFAULT_MAX_FILE_COUNT;
            return maxFileCountInt;
        } catch (Throwable e) {
            return DEFAULT_MAX_FILE_COUNT;
        }
    }

    public static CompressType buildCompressType(String compressType) {
        return CompressType.getCompressType(compressType);
    }

    public static int buildMaxFileReserveDayCount(String maxFileReserveDayCount) {
        if (StringUtils.isEmpty(maxFileReserveDayCount)) return DEFAULT_MAX_FILE_RESERVE_DAY_COUNT;
        try {
            int maxFileReserveDayCountInt = Integer.valueOf(maxFileReserveDayCount).intValue();
            if (maxFileReserveDayCountInt < 1 || maxFileReserveDayCountInt > 60) return DEFAULT_MAX_FILE_RESERVE_DAY_COUNT;
            return maxFileReserveDayCountInt;
        } catch (Throwable e) {
            return DEFAULT_MAX_FILE_RESERVE_DAY_COUNT;
        }
    }

    @Override
    public void initialize() {
        super.initialize();

        startScheduled();
    }

    private void startScheduled() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            executeTask();
        }, 1L, 5L, TimeUnit.SECONDS);
    }

    private void executeTask() {
        // 获取所有文件
        List<File> allFiles = getAllFiles();
        if (CollectionUtils.isEmpty(allFiles)) return;

        // 文件数量最大限制
        limitMaxFileCount(allFiles);

        // 文件保留天数最大限制
        limitMaxFileReserveDayCount(allFiles);

        // 所有文件大小最大限制
        limitMaxAllFileSize(allFiles);
    }

    private void limitMaxFileCount(List<File> allFiles) {
        if (CollectionUtils.isEmpty(allFiles)) return;
        if (allFiles.size() <= maxFileCount) return;

        List<File> deletedFiles = allFiles.subList(0, allFiles.size() - maxFileCount);
        deleteFiles(deletedFiles);
        allFiles.removeAll(deletedFiles);
    }

    private void limitMaxFileReserveDayCount(List<File> allFiles) {
        if (CollectionUtils.isEmpty(allFiles)) return;
        long currentTimeMillis = System.currentTimeMillis();
        Iterator<File> fileIterator = allFiles.iterator();
        while (fileIterator.hasNext()) {
            File deletedFile = fileIterator.next();
            long dayCount = (currentTimeMillis - deletedFile.lastModified()) / DateUtils.MILLIS_PER_DAY;
            if (dayCount > maxFileReserveDayCount) {
                deleteFile(deletedFile);
                fileIterator.remove();
            }
        }
    }

    private void limitMaxAllFileSize(List<File> allFiles) {
        if (CollectionUtils.isEmpty(allFiles)) return;

        int maxAllFileSizeLimitIndex = maxAllFileSizeLimitIndex(allFiles);
        if (maxAllFileSizeLimitIndex == -1) return;

        List<File> deletedFiles = allFiles.subList(0, maxAllFileSizeLimitIndex + 1);
        deleteFiles(deletedFiles);
        allFiles.removeAll(deletedFiles);
    }

    private void deleteFiles(List<File> deletedFiles) {
        if (CollectionUtils.isEmpty(deletedFiles)) return;
        for (File deletedFile : deletedFiles) {
            deleteFile(deletedFile);
        }
    }

    private int maxAllFileSizeLimitIndex(List<File> allFiles) {
        if (CollectionUtils.isEmpty(allFiles)) return -1;

        long allFileSize = 0L;
        for (int i = allFiles.size() - 1; i <= 0; i--) {
            File file = allFiles.get(i);
            allFileSize += file.length();
            if (allFileSize > maxAllFileSize) return i;
        }
        return -1;
    }

    private List<File> getAllFiles() {
        Pattern pattern = getPattern();
        if (pattern == null) return null;

        File parentFile = getFile().getParentFile();
        if (!parentFile.isDirectory() || !parentFile.exists()) return null;

        File[] files = parentFile.listFiles();
        if (files == null || files.length == 0) return null;

        List<File> allFiles = new ArrayList<>();
        for (File currFile : files) {
            if (currFile == null || !currFile.exists() || !currFile.isFile()) continue;
            String name = currFile.getName();
            if (getPattern().matcher(name).find()) {
                allFiles.add(currFile);
            }
        }

        if (allFiles.size() > 1) {
            Collections.sort(allFiles, (o1, o2) -> {
                long l = o1.lastModified() - o2.lastModified();
                if (l == 0) return 0;
                if (l > 0) return 1;
                return -1;
            });
        }
        return allFiles;
    }

    private void deleteFile(File deletedFile) {
        String name = deletedFile.getName();
        String fileNameWithExpandName = getFileNameWithExpandName();
        if (deletedFile == null || !deletedFile.isFile() || deletedFile.getName().equals(getFileNameWithExpandName())) return;
        deletedFile.delete();
        Utils.report(deletedFile.getAbsoluteFile() + " file deleted");
    }

}
